package org.dce.screencapturer.objects;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.text.AttributedString;
import java.text.BreakIterator;

import org.dce.screencapturer.components.EditorArea;
import org.dce.screencapturer.components.EditorAreaEvent;
import org.dce.screencapturer.components.EditorAreaListener;

@SuppressWarnings("serial")
public abstract class ShapeObject 
	extends DrawingObject{

	private ShapeAttributes shapeAttributes = new ShapeAttributes();

	private String text = "";
	private EditorArea area = new EditorArea();
	private boolean usingShadow = true;
	private Shape shape;

	public ShapeObject(){

		area.addEditorAreaListener(new EditorAreaListener() {
			public void editCommited(EditorAreaEvent event) {
				commitEdit();
			}
			public void editCanceled(EditorAreaEvent event) {
				cancelEdit();
			}
		});
		
	}
	
	protected void processComponentEvent(ComponentEvent event){
		if (event.getID() == ComponentEvent.COMPONENT_RESIZED
			|| event.getID() == ComponentEvent.COMPONENT_MOVED){
			rebuildShape();
		}
	}
	
	protected void rebuildShape(){
		shape = null;
	}
	
	private Shape getCurrentShape(){
		if (shape == null){
			shape = getShape();
		}
		return shape;
	}
	
	public boolean isUsingShadow() {
		return usingShadow;
	}

	public void setUsingShadow(boolean usingShadow) {
		this.usingShadow = usingShadow;
	}

	public LineAttributes getLineAttributes() {
		return shapeAttributes.getLineAttributes();
	}

	public ShapeAttributes getShapeAttributes() {
		return shapeAttributes;
	}

	public void setShapeAttributes(ShapeAttributes shapeAttributes) {
		ShapeAttributes oldValue = this.shapeAttributes;
		this.shapeAttributes = shapeAttributes;
		firePropertyChange("shapeAttributes", oldValue, shapeAttributes);
		repaint();
	}

	public void setLineAttributes(LineAttributes lineAttributes) {
		LineAttributes oldValue = this.shapeAttributes.getLineAttributes();
		this.shapeAttributes.setLineAttributes(lineAttributes);
		firePropertyChange("lineAttributes", oldValue, lineAttributes);
		repaint();
	}

	public ShadowAttributes getShadowAttributes() {
		return shapeAttributes.getShadowAttributes();
	}
	
	public void setShadowAttributes(ShadowAttributes shadowAttributes){
		ShadowAttributes oldValue = this.shapeAttributes.getShadowAttributes();
		this.shapeAttributes.setShadowAttributes(shadowAttributes);
		firePropertyChange("shadowAttributes", oldValue, shadowAttributes);
		repaint();
	}
	
	public FillAttributes getFillAttributes() {
		return shapeAttributes.getFillAttributes();
	}

	public void setFillAttributes(FillAttributes fillAttributes) {
		FillAttributes oldValue = this.shapeAttributes.getFillAttributes();
		this.shapeAttributes.setFillAttributes(fillAttributes);
		firePropertyChange("fillAttributes", oldValue, fillAttributes);
		repaint();
	}

	public TextAttributes getTextAttributes() {
		return shapeAttributes.getTextAttributes();
	}

	public void setTextAttributes(TextAttributes textAttributes) {
		TextAttributes oldValue = this.shapeAttributes.getTextAttributes();
		this.shapeAttributes.setTextAttributes(textAttributes);
		firePropertyChange("textAttributes", oldValue, textAttributes);
		repaint();
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

	@Override
	public boolean contains(int x, int y) {
		return getCurrentShape().contains(x,y);
	}

	public void draw(Graphics2D g) {

		Shape shape = getCurrentShape();
		
		g.setRenderingHint(
				RenderingHints.KEY_ANTIALIASING, 
				RenderingHints.VALUE_ANTIALIAS_ON);
		
		Rectangle innerBounds;
		
		FillAttributes fillAttributes = shapeAttributes.getFillAttributes();
		LineAttributes lineAttributes = shapeAttributes.getLineAttributes();
		TextAttributes textAttributes = shapeAttributes.getTextAttributes();
		ShadowAttributes shadowAttributes = shapeAttributes.getShadowAttributes();
		
		boolean fill = supportsFill() && fillAttributes.isEnabled();
		boolean paint = supportsPaint() && lineAttributes.isEnabled();
		
		if (shape != null){
			
			if (shadowAttributes.isEnabled()){
		
				shadowAttributes.applyAttributes(g);
				g.setColor(Color.BLACK);
				
				if (fill){
					g.fill(shape);
				} else {
					
					Stroke s = g.getStroke();
					
					g.setStroke(lineAttributes.getStroke());
					
					g.draw(shape);
					
					g.setStroke(s);
				}
				shadowAttributes.restore(g);
			}
			
			if (fill){
				fillAttributes.applyAttributes(g);
				g.fill(shape);
				fillAttributes.restore(g);
			}
			
			if (paint){
				lineAttributes.apply(g);
				g.draw(shape);
				lineAttributes.restore(g);
			}
			
			innerBounds = calculateInnerBounds(shape);
		} else {
			innerBounds = new Rectangle(0,0,getWidth(),getHeight());
		}
		
		if (supportsText()
			&& text != null 
			&& text.trim().length() > 0){
			
			renderText(g,textAttributes,text,innerBounds);
		}
	}

	protected void renderText(Graphics2D g, TextAttributes textAttributes, String text, Rectangle innerBounds) {
		
		g.setColor(textAttributes.getColor());
		
		AttributedString string = new AttributedString(text,textAttributes.getFont().getAttributes());
		
		BreakIterator breakIterator = BreakIterator.getSentenceInstance();
		
		LineBreakMeasurer measurer = new LineBreakMeasurer(
				string.getIterator(), 
				breakIterator, 
				g.getFontRenderContext());

		g = (Graphics2D)g.create(innerBounds.x, innerBounds.y, innerBounds.width, innerBounds.height);
		
		int yOffset = 0;
		int totalHeight = 0;

		for (TextLayout layout = measurer.nextLayout(innerBounds.width);
			layout != null; 
			layout = measurer.nextLayout(innerBounds.width)){
			totalHeight+=(int)layout.getBounds().getHeight();
		}
		switch (textAttributes.getVerticalAlignment()){
		case TOP: // 0
			break;
		case BOTTOM:
			yOffset = innerBounds.height - totalHeight;
			break;
		case MIDDLE:
			yOffset = (innerBounds.height - totalHeight)/2;
			break;
		}
		
		measurer.setPosition(0);
		
		for (TextLayout layout = measurer.nextLayout(innerBounds.width);
			layout != null; 
			layout = measurer.nextLayout(innerBounds.width)){

			int xOffset = 0;
			
			Rectangle2D layoutBounds = layout.getBounds();
			
			switch (textAttributes.getHorizontalAlignment()){
			case CENTER:
				xOffset = (int)((innerBounds.width - layoutBounds.getWidth())/2);
				break;
			case LEFT: // 0
				break;
			case RIGHT:
				xOffset = (int)(innerBounds.width - layoutBounds.getWidth());
				break;
			}
			
			yOffset += (int)layoutBounds.getHeight();
			
			AffineTransform transform = new AffineTransform();
			transform.translate(xOffset, yOffset);
			
			g.setColor(textAttributes.getColor());
			g.fill(layout.getOutline(transform));
		}
		
	}

	protected boolean supportsText(){
		return true;
	}
	
	protected boolean supportsFill(){
		return true;
	}

	protected boolean supportsPaint(){
		return true;
	}

	protected Rectangle calculateInnerBounds(Shape shape) {
		return shape.getBounds();
	}
	
	protected Rectangle calculateInnerBounds(){
		return calculateInnerBounds(getCurrentShape());
	}

	@Override
	protected void processMouseEvent(MouseEvent e) {
		
		if (isEditorTrigger(e)){
			showEditor();
		} else {
			super.processMouseEvent(e);
		}
	}

	private boolean isEditorTrigger(MouseEvent e) {
		return e.getID() == MouseEvent.MOUSE_CLICKED
			&& e.getButton() == MouseEvent.BUTTON1
			&& e.getClickCount() == 2;
	}
	
	private void showEditor(){
		area.setBounds(calculateInnerBounds());
		
		area.setForeground(Color.BLACK);
		area.setBackground(Color.WHITE);
		
		area.setOpaque(true);
		
		add(area);
		revalidate();
		repaint();
		area.requestFocus();
	}
	
	private void cancelEdit(){
		hideEditor();
	}
	
	private void commitEdit(){
		text = area.getText();
		hideEditor();
	}
	
	private void hideEditor(){
		area.setOpaque(false);
		rebuildShape();
		remove(area);
		revalidate();
		repaint();
	}

	protected abstract Shape getShape();

}
